# coding=UTF-8
"""
Copyright Ian Ross Williams, 2012

    This file is part of ArcSecond.

    ArcSecond is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ArcSecond is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ArcSecond.  If not, see <http://www.gnu.org/licenses/>.
"""

import pygtk
pygtk.require('2.0')
import gtk.gdk
import numpy as np
import scipy.linalg as linalg
import scipy.integrate as integrate
import scipy.optimize as optimize
import scipy.stats as stats
import Image,ImageOps,ImageDraw
import pywt
import StringIO
import os
import thread
import time
from arcsecond import MFBDshapelet
from arcsecond import MFBC
from arcsecond import filters

class notebook_stack():
    image = None           #The contents of the drawing area
    width = 640             
    height = 480        
    statusbarLeft=None
    statusbarLeftID=None
    frameList = []
    shift = []
    alive=True
    stack_thread_alive=False

    FFTEnabled=False
    ROI = None
    maskEnabled = True
    stackInGroups = 0
    histogramCorrectionEnabled=False
    blackPoint = 0.0
    whitePoint = 1.0
    gammaCorrection = 1.0
    percentile=0.5    
    spectralMidFrequency=0.5
    spectralMidRank=0.5
    spectralHighRank=0.5    
    noOfSingularValues = 3
    MFBD_initialGuessMethod=2
    MFBD_shapeletSize=8.
    MFBD_psfSize=32
    MFBD_noOfFrames=1
    MFBD_shapeletOrder=5
    MFBD_Eiterations=2
    MFBD_Miterations=5
    MFBD_waveletThresholdEnabled=False
    MFBD_LRThreshold=0
    MFBD_spectralNoiseFilter=0.0
    MFBC_distance=10.
    kappa=0.5

    (METHOD_MEAN, 
     METHOD_MEDIAN, 
     METHOD_RANK,
     METHOD_KAPPASIGMA,
     METHOD_KAPPASIGMAMEDIAN,
     METHOD_SVD,
     METHOD_KALMAN,
     METHOD_FFTRANK,
     METHOD_MFBD_SHAPELET,
     METHOD_MFBC)=range(10)
    method = METHOD_MEAN
    
    (DATAMODE_IMAGE,
     DATAMODE_FFT,
     DATAMODE_WAVELET)=range(3)
    dataMode = DATAMODE_IMAGE
    
    (COLOUR_GREY,
     COLOUR_RGB,
     COLOUR_LRGB)=range(3)
    colour=COLOUR_GREY
            
    def __init__(self,statusbar,id):
        self.statusbarLeft=statusbar
        self.statusbarLeftID=id

    def __del__(self):
        self.buttonStop_callback()
                
    def buttonStop_callback(self,widget=None):
        self.statusbarLeft.push(self.statusbarLeftID,"Stopping...") 
        self.alive=False
        while self.stack_thread_alive: time.sleep(0.1)
        self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 

    def buttonStack_callback(self, button):
        print "Stack pressed"
        thread.start_new_thread(self.stack_thread,()) 
        #self.stack_thread() 

    def new(self):
        if len(self.frameList)>0:
            self.image = Image.open(self.frameList[0])
            self.width, self.height = self.image.size
            self.drawingArea.set_size_request(self.width, self.height)
        else:
            self.image = None
        self.drawingArea.queue_draw()
        
    def clear_roi(self):
        self.ROI=None
        self.statusbarLeft.push(self.statusbarLeftID,"Cleared Region of Interest.") 
        self.drawingArea.queue_draw()

    def show_necessary_controls(self):
        self.maskButton.set_visible     (self.method<=self.METHOD_SVD)
        self.frameKappa.set_visible     (self.method==self.METHOD_KAPPASIGMA or self.method==self.METHOD_KAPPASIGMAMEDIAN )
        self.framePercentile.set_visible(self.method==self.METHOD_RANK)
        self.frameSVD.set_visible       (self.method==self.METHOD_SVD)        
        self.frameMFBDPSFSize.set_visible           (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameMFBDnoOfFrames.set_visible        (self.method==self.METHOD_MFBD_SHAPELET or self.method==self.METHOD_MFBC)        
        self.frameMFBDinitialGuessMethod.set_visible(self.method==self.METHOD_MFBD_SHAPELET or self.method==self.METHOD_MFBC)
        self.frameMFBDshapeletSize.set_visible      (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameMFBDEiterations.set_visible       (self.method==self.METHOD_MFBD_SHAPELET or self.method==self.METHOD_MFBC)
        self.frameMFBDMiterations.set_visible       (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameMFBDShapeletOrder.set_visible     (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameMFBDLRThreshold.set_visible       (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameMFBDTVScaling.set_visible         (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameDistance.set_visible              (self.method==self.METHOD_MFBC)
        self.MFBDwaveletThresholdButton.set_visible (self.method==self.METHOD_MFBD_SHAPELET)
        self.frameSpectralMidFreq.set_visible(self.method==self.METHOD_FFTRANK)
        self.frameSpectralMidRank.set_visible(self.method==self.METHOD_FFTRANK)                
        self.frameSpectralHighRank.set_visible(self.method==self.METHOD_FFTRANK)        
        self.hboxBlackAndWhite.set_visible(self.histogramCorrectionEnabled)
        self.frameGamma.set_visible(self.histogramCorrectionEnabled)
        
    def combobox_method_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.method = active
        self.show_necessary_controls()

    def combobox_colour_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.colour = active
    
    def combobox_data_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.dataMode = active
                       
    def mask_toggled(self, button):
        self.maskEnabled = not self.maskEnabled
        
    def stackInGroups_changed(self, spinbutton):
        self.stackInGroups = spinbutton.get_value()

    def kappa_changed(self, spinbutton):
        self.kappa = spinbutton.get_value()

    def percentile_changed(self, spinbutton):
        self.percentile = spinbutton.get_value()

    def histogramCorrection_toggled(self, button):
        self.histogramCorrectionEnabled = not self.histogramCorrectionEnabled
        self.hboxBlackAndWhite.set_visible(self.histogramCorrectionEnabled)
        self.frameGamma.set_visible(self.histogramCorrectionEnabled)
        self.drawingArea.queue_draw()
        
    def black_changed(self, spinbutton):
        self.blackPoint = spinbutton.get_value()
        self.drawingArea.queue_draw()

    def white_changed(self, spinbutton):
        self.whitePoint = spinbutton.get_value()
        self.drawingArea.queue_draw()

    def gamma_changed(self, spinbutton):
        self.gammaCorrection = spinbutton.get_value()
        self.drawingArea.queue_draw()

    def svd_changed(self, spinbutton):
        self.noOfSingularValues = spinbutton.get_value()

    def noOfFrames_changed(self, spinbutton):
        self.MFBD_noOfFrames = spinbutton.get_value()
        
    def combobox_MFBDinitialGuessMethod_changed(self, cb):
        model = cb.get_model()
        active = cb.get_active()
        if active < 0: return None
        self.MFBD_initialGuessMethod = active
       
    def psfSize_changed(self, spinbutton):
        self.MFBD_psfSize = spinbutton.get_value()
        self.adjustmentMFBDshapeletSize.set_upper(self.MFBD_psfSize)
    
    def MFBDshapeletSize_changed(self, spinbutton):
        self.MFBD_shapeletSize = spinbutton.get_value()

    def MFBDShapeletOrder_changed(self, spinbutton):
        self.MFBD_shapeletOrder = spinbutton.get_value()

    def MFBDwaveletThreshold_toggled(self, button):
        self.MFBD_waveletThresholdEnabled = not self.MFBD_waveletThresholdEnabled

    def MFBDLRThreshold_changed(self, spinbutton):
        self.MFBD_LRThreshold = spinbutton.get_value()

    def MFBDTVScaling_changed(self, spinbutton):
        self.MFBD_spectralNoiseFilter = spinbutton.get_value()

    def Eiterations_changed(self, spinbutton):
        self.MFBD_Eiterations = spinbutton.get_value()
    
    def distance_changed(self, spinbutton):
        self.MFBC_distance = spinbutton.get_value()

    def Miterations_changed(self, spinbutton):
        self.MFBD_Miterations = spinbutton.get_value()

    def spectralMidFreq_changed(self, scale):
        self.spectralMidFreq = scale.get_value()
        
    def spectralMidRank_changed(self, scale):
        self.spectralMidRank = scale.get_value()
        
    def spectralHighRank_changed(self, scale):
        self.spectralHighRank = scale.get_value()

    def set_frames(self, frameList, shift):
        self.shift = shift

        self.frameList=[]
        for i,frame in enumerate(frameList):
            path = os.path.dirname(frame) + "/registered"
            registeredFile=path + "/" + os.path.basename(frame)
            fname=frame
            if os.path.exists(registeredFile):
                fname = registeredFile
            self.frameList.append(fname)

        if len(self.frameList)>0 and self.image==None:
            self.image = Image.open(self.frameList[0])
            self.width, self.height = self.image.size
            self.drawingArea.set_size_request(self.width, self.height)
            self.drawingArea.queue_draw()
                        
        noOfFrames=len(self.frameList)
        self.adjustmentSubGroups.set_upper(max(1,noOfFrames/3))
        self.adjustmentNoOfFrames.set_upper(noOfFrames)
        self.adjustmentNoOfFrames.set_value(noOfFrames)
        
        msg="%d frames to stack" % noOfFrames
        self.statusbarLeft.push(self.statusbarLeftID, msg)

    def selected(self):
        self.buttonStack.grab_default()
        self.buttonStack.grab_focus()

    # Create a new backing pixmap of the appropriate size
    def configure_event(self, widget, event):
        x, y, width, height = widget.get_allocation()
        pixmap = gtk.gdk.Pixmap(widget.window, width, height)
        pixmap.draw_rectangle(widget.get_style().white_gc,
                              True, 0, 0, width, height)
    
    # Displays the currently selected frame 
    def expose_event(self, widget, event):
        if self.image!=None:
            im = self.image.copy()
            self.width,self.height=im.size
            if self.width<=0 or self.height<=0: return
            
            if im.mode=="L" and self.histogramCorrectionEnabled:
                L = np.asarray(im.convert("L"),np.float32)
              
                if self.ROI==None and (self.method==self.METHOD_MFBD_SHAPELET):
                    ROI = [self.MFBD_psfSize/2,self.MFBD_psfSize/2,self.width-self.MFBD_psfSize/2,self.height-self.MFBD_psfSize/2]
                else:
                    ROI = self.ROI

                if ROI!=None: 
                    Lroi = L[ROI[1]:ROI[3],ROI[0]:ROI[2]]
                else: 
                    Lroi = L
                mn,mx = np.min(Lroi),np.max(Lroi)

                #White/Black point correction
                mnNew = self.blackPoint*(mx-mn)
                mxNew = self.whitePoint*(mx-mn)
                Lroi=(Lroi-mnNew)/max(1., (mxNew-mnNew))
                Lroi=np.clip(Lroi,0,1)
                       
                #Gamma correction
                Lroi=Lroi**(self.gammaCorrection)
                mn,mx = np.min(Lroi),np.max(Lroi)                
                Lroi=255.*(Lroi-mn)/max(1., (mx-mn))
                if ROI!=None: 
                    L[ROI[1]:ROI[3],ROI[0]:ROI[2]]=Lroi
                else: 
                    L = Lroi
                im = Image.fromarray(L).convert("L")

            if self.ROI:
                draw=ImageDraw.Draw(im)
                draw.rectangle(self.ROI)

            pixbuf=self.image2pixbuf(im)
            widget.window.draw_pixbuf(None,pixbuf, 0, 0, 0, 0, self.width, self.height) 

    def button_press_event(self, widget, event):
        if event.button == 1 and self.image != None:
            self.ROI = [event.x,event.y,event.x,event.y]
            self.statusbarLeft.push(self.statusbarLeftID,
               "Region of interest: [%d,%d] to [%d,%d], size=%dx%d" % (self.ROI[0],self.ROI[1],self.ROI[2],self.ROI[3],0,0) ) 


    def button_release_event(self, widget, event):
        if event.button == 1 and self.image != None:
            self.ROI = [min(self.ROI[0],event.x),min(self.ROI[1],event.y),
                        max(self.ROI[0],event.x),max(self.ROI[1],event.y)]
            sx = self.ROI[2] - self.ROI[0]
            sy = self.ROI[3] - self.ROI[1]
            if sx<=0 or sy<=0:
                self.ROI = None
                self.statusbarLeft.push(self.statusbarLeftID, "Region of interest cleared.")
            else:
                self.statusbarLeft.push(self.statusbarLeftID,
                   "Region of interest: [%d,%d] to [%d,%d], size=%dx%d" % (self.ROI[0],self.ROI[1],self.ROI[2],self.ROI[3],sx,sy) ) 
            self.drawingArea.queue_draw()

    def motion_notify_event(self,widget,event): 
        if event.is_hint:
            x, y, state = event.window.get_pointer()
        else:
            x = event.x
            y = event.y
            state = event.state
        
        if state & gtk.gdk.BUTTON1_MASK and self.image != None and self.ROI!=None:
            self.ROI = [self.ROI[0],self.ROI[1],x,y]
            displayROI = [min(self.ROI[0],x),min(self.ROI[1],y),
                        max(self.ROI[0],x),max(self.ROI[1],y)]
            sx = displayROI[2] - displayROI[0]
            sy = displayROI[3] - displayROI[1]
            self.statusbarLeft.push(self.statusbarLeftID,
               "Region of interest: [%d,%d] to [%d,%d], size=%dx%d" % (displayROI[0],displayROI[1],displayROI[2],displayROI[3],sx,sy) ) 
            self.drawingArea.queue_draw()
            
        return True

    def get_ROI(self):
        return self.ROI

    # From Alexandre Passos' research blog
    # http://atpassos.posterous.com/fast-truncated-svd-using-random-projections
    def fast_svd(self, M, k):
        p = k+5
        Y = np.dot(M, np.random.normal(size=(M.shape[1],p)))
        Q,r = linalg.qr(Y)
        B = np.dot(Q.T,M)
        Uhat, s, v = linalg.svd(B, full_matrices=False)
        U = np.dot(Q, Uhat)
        return U.T[:k].T, s[:k], v[:k]

    def stack_thread(self):
        print "Stack thread"
        if self.stack_thread_alive: 
            print "buttonStack_callback: Still stacking"
            with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Still stacking.")
            return

        if self.ROI==None and self.method==self.METHOD_MFBD_SHAPELET:
            with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Please select a Region of Interest before using MFBD")
            self.stack_thread_alive=False
            return 

        self.alive=True
        self.stack_thread_alive=True
        
        if   self.colour==self.COLOUR_GREY: channels = ["L"]
        elif self.colour==self.COLOUR_RGB:  channels = ["R","G","B"]
        elif self.colour==self.COLOUR_LRGB: channels = ["R","G","B","L"]

        basepath = os.path.split(self.frameList[0])[0]
        os.chdir(basepath)
        
        noOfFrames=len(self.frameList)
        g=self.stackInGroups
        if g==0: g=noOfFrames
        noOfGroups=int(noOfFrames/g)
        for group in xrange(noOfGroups):
            frame0=group*g
            frame1=max(noOfFrames,frame0+g)
            self.width,self.height=0,0
            result = []
            for chNo,channel in enumerate(channels):
                stack=[]
                nimFirstFrame=None
                for frameNoOfGroup,f in enumerate(self.frameList[frame0:frame1]):
                    if not self.alive: 
                        with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 
                        self.stack_thread_alive=False
                        return

                    #Display the status
                    print "Loading %s" % f
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,channel + " %d of %d" %(frame0 + frameNoOfGroup + 1,noOfFrames))
                    
                    #Open the current frame
                    try:
                        path = os.path.dirname(f) + "/registered"
                        registeredFile=path + "/" + os.path.basename(f)
                        if os.path.exists(registeredFile):
                            im = Image.open(registeredFile)
                        else:
                            im = Image.open(f)
                    except AttributeError:
                        pass

                    if self.width==0 or self.height==0:
                        self.width,self.height=im.size
                    elif self.width!=im.size[0] or self.height!=im.size[1]:
                        newImage = Image.new(im.mode, (self.width,self.height))
                        newImage.paste(im, (0,0))
                        im = newImage
                        
                    if channel=="R" or channel=="G" or channel=="B":
                        im.load()
                        im = im.split()[chNo]
                        im = im.convert("L")
                    else:
                        im = im.convert("L")
                        
                    nim = np.asarray(im,np.float32)
                    if len(nim.shape)>2: nim=np.mean(nim,axis=2)
                    if nimFirstFrame==None: nimFirstFrame=nim
                    
                    if result==None:
                        result = np.zeros( (len(channels),nim.shape[0],nim.shape[1]) )

                    #Crop out the ROI
                    if self.ROI!=None and not (self.method==self.METHOD_MFBD_SHAPELET or self.method==self.METHOD_MFBC):
                        fullnim = np.array(nimFirstFrame)
                        nim = np.array(nim[self.ROI[1]:self.ROI[3],self.ROI[0]:self.ROI[2]])

                    #Apply any transforms to the data here
                    if self.dataMode==self.DATAMODE_FFT:
                        nim = np.fft.rfft2(nim)
                        nim = np.fft.fftshift(nim)
                    elif self.dataMode==self.DATAMODE_WAVELET:
                        levels  = int( np.log2(nim.shape[0]) )
                        #wavelet = pywt.Wavelet('haar')
                        wavelet = pywt.Wavelet('bior1.3')
                        coeffs = pywt.wavedec2(nim,wavelet,level=levels)
                        cA = coeffs[0]
                        nim = cA.flatten()
                        cH = [ coeffs[l+1][0] for l in xrange(levels) ]
                        cV = [ coeffs[l+1][1] for l in xrange(levels) ]
                        cD = [ coeffs[l+1][2] for l in xrange(levels) ]                                
                        for l in xrange(levels):
                            nim = np.append(nim, cH[l].flatten())
                            nim = np.append(nim, cV[l].flatten())
                            nim = np.append(nim, cD[l].flatten())
                                                                
                    #Flatten the data and build a stack
                    if len(stack)==0:
                        nim = np.array(nim)
                        stack = np.array([nim.flatten()])
                    else:
                        nim = np.array(nim)
                        stack = np.append(stack, [nim.flatten()], axis=0)

                #Create a mask to ignore under or over-exposed pixels
                if self.maskEnabled and (self.method==self.METHOD_MEAN or self.method==self.METHOD_MEDIAN or self.method==self.METHOD_RANK or self.method==self.METHOD_KAPPASIGMA or self.method==self.METHOD_KAPPASIGMAMEDIAN or self.method==self.METHOD_SVD):
                    mask = (stack==0) | (stack==255)
                    stack = np.ma.array(stack,mask=mask)
                    
                # Combine the frames of the stack
                if self.method==self.METHOD_MEAN:
                    stack=np.ma.mean(stack, axis=0)
                    result.append(stack)
                elif self.method==self.METHOD_MEDIAN:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Median stacking...") 
                    stack=np.ma.median(stack, axis=0,overwrite_input=True)
                    result.append(stack)
                elif self.method==self.METHOD_RANK or self.method==self.METHOD_FFTRANK:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Rank stacking...") 
                    
                    stack = stack
                    if self.method==self.METHOD_RANK: 
                        p=np.ones(nim.shape)*self.percentile
                    else:
                        ox,oy = nim.shape[0]/2., nim.shape[1]/2.
                        rmax = np.sqrt(ox**2 + oy**2)                        
                        xx,yy = np.meshgrid(xrange(nim.shape[0]),xrange(nim.shape[1]))
                        w = np.sqrt((xx-ox)**2 + (yy-oy)**2) / rmax
                        wmid  = self.spectralMidFrequency
                        pmid  = self.spectralMidRank
                        phigh = self.spectralHighRank
                        print "wmid,pmid,phigh = ", wmid,pmid,phigh
                        a2 = (2.*pmid - 1. - 2.*wmid*phigh + wmid) / (2.*wmid*(wmid-1.))
                        a1 = (2.*wmid**2*phigh - wmid**2 - 2.*pmid + 1.) / (2.*wmid*(wmid-1.))
                        p = 0.5 + a1*w + a2*w**2
                        p = np.ma.clip(p,0.0,1.0)
                        
                    s = np.ma.sort(stack,axis=0)
                    k = (s.shape[0]-1.) * p.flatten()
                    f = np.ma.floor(k).astype(int)
                    c = np.ma.ceil (k).astype(int)
                    d0 = [ s[fi,i] for i,fi in enumerate(f) ] * (c-k)
                    d1 = [ s[ci,i] for i,ci in enumerate(c) ] * (k-f)
                    stack=d0+d1
                    if np.ma.any(f==c):
                        stack[f==c] = s[f, f==c]
                    result.append(stack)

                elif self.method==self.METHOD_KAPPASIGMA:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Kappa-sigma stacking...") 
                    median=np.ma.median(stack, axis=0)
                    mean=np.ma.mean(stack, axis=0)
                    std =np.std (stack, axis=0)
                    out =np.zeros(mean.shape)
                    count=np.zeros(mean.shape)
                    for i,frame in enumerate(stack):
                        mask=np.abs(frame-mean) <= self.kappa*std
                        out[mask]=out[mask]+frame[mask]
                        count[mask]=count[mask]+1.
                    count[count==0]=1
                    stack=out/count
                    result.append(stack)

                elif self.method==self.METHOD_KAPPASIGMAMEDIAN:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Kappa-sigma-median stacking...") 
                    noOfFrames=stack.shape[0]
                    median=np.ma.median(stack, axis=0)
                    mean=np.ma.mean(stack, axis=0)
                    std =np.ma.std (stack, axis=0)
                    out =np.ma.zeros(mean.shape)
                    for i,frame in enumerate(stack):
                        mask=np.ma.abs(frame-mean) <= self.kappa*std
                        out[mask]=out[mask]+frame[mask]
                        mask=np.abs(frame-mean) > self.kappa*std
                        out[mask]=out[mask]+median[mask]
                    stack=out/noOfFrames
                    result.append(stack)

                elif self.method==self.METHOD_SVD:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Single Value Decomposition stacking...") 
                    U,s,V=self.fast_svd(stack, self.noOfSingularValues)
                    S = np.diag(s)
                    stack = np.ma.dot(U, np.dot(S, V))
                    stack = np.ma.median(stack,axis=0)
                    result.append(stack)
                    
                elif self.method==self.METHOD_KALMAN:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Kalman filtering...") 
                    xhat = np.ma.mean(stack, axis=0)   #A posteriori estimate of x
                    P         = np.ones(xhat.shape)   #A posteriori error estimate
                    xhatMinus = np.zeros(xhat.shape)  #A priori estimate of x
                    Pminus    = np.zeros(xhat.shape)  #A priori error estimate
                    K         = np.zeros(xhat.shape)  #Gain or blending factor
                    Q = np.ma.std(stack,axis=0) #Process noise
                    R = np.ma.var(stack,axis=0) #Estimate of measurement error variance
                    for frame in stack:
                        xhatMinus[:] = xhat
                        Pminus[:] = P + Q
                        K[:] = Pminus / (Pminus + R)
                        xhat[:] = xhatMinus + K*(frame-xhatMinus)
                        P[:] = (1. - K) * Pminus
                    stack = xhat
                    result.append(stack)
                    
                elif self.method==self.METHOD_MFBD_SHAPELET and self.dataMode==self.DATAMODE_IMAGE and self.colour==self.COLOUR_GREY:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Multi-Frame Blind Deconvolution stacking with Shaplet PSFs...") 
                    mfbd = MFBDshapelet.MFBD()
                    stack = mfbd.MFBD(stack,nim.shape,self.MFBD_psfSize,self.MFBD_shapeletSize/2.,
                                      roi=self.ROI,callback=self.callback_for_MFBD,
                                      Eiterations=self.MFBD_Eiterations,Miterations=self.MFBD_Miterations,
                                      noOfFrames=self.MFBD_noOfFrames,
                                      LRThreshold=self.MFBD_LRThreshold,
                                      lambdaTV=self.MFBD_spectralNoiseFilter,
                                      waveletFilter=self.MFBD_waveletThresholdEnabled,
                                      methodInitialGuess=self.MFBD_initialGuessMethod, 
                                      shapeletOrder=self.MFBD_shapeletOrder)
                    if stack==None:
                        with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 
                        self.stack_thread_alive=False
                        return
                    
                    result.append(stack)

                elif self.method==self.METHOD_MFBD_SHAPELET and self.dataMode==self.DATAMODE_IMAGE and (self.colour==self.COLOUR_RGB or self.colour==self.COLOUR_LRGB):
                    if self.colour==self.COLOUR_RGB:
                        if channel!="B": continue
                    if self.colour==self.COLOUR_LRGB:
                        if channel!="L": continue
                        
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Multi-Frame Blind Deconvolution stacking with Shaplet PSFs RGB...") 
                    mfbd = MFBDshapelet.MFBD()
                    stack = np.mean(channelStack,axis=0)
                    result = mfbd.MFBD(stack,nim.shape,self.MFBD_psfSize,self.MFBD_shapeletSize/2.,
                                       stackExtra=channelStack[0:3],
                                       roi=self.ROI,callback=self.callback_for_MFBD,
                                       Eiterations=self.MFBD_Eiterations,Miterations=self.MFBD_Miterations,
                                       noOfFrames=self.MFBD_noOfFrames,
                                       LRThreshold=self.MFBD_LRThreshold,
                                       lambdaTV=self.MFBD_spectralNoiseFilter,
                                       waveletFilter=self.MFBD_waveletThresholdEnabled,
                                       methodInitialGuess=self.MFBD_initialGuessMethod, 
                                       shapeletOrder=self.MFBD_shapeletOrder)
                  
                    if result==None:
                        with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 
                        self.stack_thread_alive=False
                        return

                    #Ignore the luminance channel from the result if using RGB only
                    if self.colour==self.COLOUR_RGB:
                        result = result[1:]

                elif self.method==self.METHOD_MFBC and self.dataMode==self.DATAMODE_IMAGE and self.colour==self.COLOUR_GREY:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Multi-Frame Convection stacking...") 
                    mfbc = MFBC.MFBC()
                    stack = mfbc.MFBC(stack,nim.shape,
                                      roi=self.ROI,callback=self.callback_for_MFBD,
                                      distance=self.MFBC_distance,
                                      globalIterations=10,                                      
                                      fittingIterations=self.MFBD_Eiterations,
                                      noOfFrames=self.MFBD_noOfFrames,
                                      underSample=100,
                                      methodInitialGuess=self.MFBD_initialGuessMethod)

                    if stack==None:
                        with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 
                        self.stack_thread_alive=False
                        return
                    
                    result.append(stack)
                    
                else:
                    with gtk.gdk.lock: self.statusbarLeft.push(self.statusbarLeftID,"Incompatible method/datamode") 
                    self.stack_thread_alive=False
                    return
                    
            #Convert the stacked frame back to pixel images
            for chNo,channel in enumerate(channels):
                stack = result[chNo]
                if self.dataMode==self.DATAMODE_IMAGE:
                    print stack.shape, nim.shape
                    stack = np.reshape(stack, (nim.shape[0],nim.shape[1]) )
                if self.dataMode==self.DATAMODE_FFT:
                    stack = np.reshape(stack, (nim.shape[0],nim.shape[1]) )
                    stack = np.fft.ifftshift(stack)
                    stack = np.fft.irfft2(stack)
                elif self.dataMode==self.DATAMODE_WAVELET:
                    i1 = len(cA.flatten())
                    cA = stack[:i1].reshape(cA.shape)
                    coeffs = [cA]
                    for l in xrange(levels):
                        i2 = i1 + len(cH[l].flatten())
                        i3 = i2 + len(cV[l].flatten())
                        i4 = i3 + len(cD[l].flatten())
                        cHn = stack[i1 :i2 ].reshape(cH[l].shape)
                        cVn = stack[i2 :i3 ].reshape(cV[l].shape)
                        cDn = stack[i3 :i4 ].reshape(cD[l].shape)
                        coeffs.append((cHn,cVn,cDn))
                        i1 = i4
                    stack = pywt.waverec2(coeffs,wavelet)

                #If the image was cropped, paste it back into the full scene
                if self.ROI!=None and not (self.method==self.METHOD_MFBD_SHAPELET or self.method==self.METHOD_MFBC):
                    y1 = self.ROI[1] + stack.shape[0]
                    x1 = self.ROI[0] + stack.shape[1]
                    fullnim[self.ROI[1]:y1,self.ROI[0]:x1] = stack
                    stack = fullnim

                #Store the result
                result[chNo] = np.array(stack)

                o=Image.fromarray(np.clip(stack,0,255)).convert("L")
                o.save("stack_%04d_%s.png" % (group,channel) )

                #Display the stacked channel
                self.image = o
                self.drawingArea.queue_draw()
                
            if not self.alive:
                self.stack_thread_alive=False
                self.statusbarLeft.push(self.statusbarLeftID,"Stopped") 
                return
        
            #If there isn't a luminance channel then make one
            print "L"
            if channels[0]=="L": 
                L = result[0]
            elif "L" in channels: 
                L = result[3]
            else:
                L = np.mean(result,axis=0)

            #Gamma correction
            if self.histogramCorrectionEnabled:
                print "HISTOGRAM"

                if self.ROI==None and (self.method==self.METHOD_MFBD_SHAPELET):
                    ROI = [self.MFBD_psfSize,self.MFBD_psfSize,self.width-self.MFBD_psfSize,self.height-self.MFBD_psfSize]
                else:
                    ROI = self.ROI

                if ROI!=None: 
                    Lroi = L[ROI[1]:ROI[3],ROI[0]:ROI[2]]
                else: 
                    Lroi = L
                
                #White/Black point correction
                mn,mx = np.min(Lroi),np.max(Lroi)
                mnNew = self.blackPoint*(mx-mn)
                mxNew = self.whitePoint*(mx-mn)
                Lroi=(Lroi-mnNew)/max(1.,(mxNew-mnNew))
                Lroi=np.clip(Lroi,0,1)
                Lroi=(Lroi**self.gammaCorrection)
                mn,mx = np.min(Lroi),np.max(Lroi)
                Lroi=255.*(Lroi-mn)/max(1.,(mx-mn))

                if ROI!=None: 
                    L[ROI[1]:ROI[3],ROI[0]:ROI[2]] = Lroi
                else: 
                    L = Lroi
                
                if "R" in channels:
                    for ri,r in enumerate(result):
                        if ri==3: continue

                        if ROI!=None: 
                            rroi = r[ROI[1]:ROI[3],ROI[0]:ROI[2]]
                        else: 
                            rroi = r

                        mn,mx = np.min(rroi),np.max(rroi)
                        rroi = (rroi-mn)/max(1.,(mx-mn))
                        rroi = np.clip(rroi,0,1)
                        rroi=rroi**self.gammaCorrection
                        mn,mx = np.min(rroi),np.max(rroi)
                        rroi = 255.*(rroi-mn)/max(1.,(mx-mn))

                        if ROI!=None: 
                            r[ROI[1]:ROI[3],ROI[0]:ROI[2]] = rroi
                        else: 
                            r = rroi


                #Reset the parameters
                self.blackPoint=0.0
                self.whitePoint=1.0
                self.gammaCorrection=1.0
                self.adjustmentWhite.set_value(self.whitePoint)
                self.adjustmentBlack.set_value(self.blackPoint)
                self.adjustmentGamma.set_value(self.gammaCorrection)

            #Display the luminance channel
            imL= Image.fromarray(L).convert("L")
            self.image=imL
            self.drawingArea.queue_draw()

            #Combine the channels as required
            if "R" in channels:  
                print "R",
                R = result[0]
                if "G" in channels:
                    print "G",  
                    G = result[1]
                    if "B" in channels:
                        print "B",  
                        B = result[2]
                        imR = Image.fromarray(R).convert("L")
                        imG = Image.fromarray(G).convert("L")
                        imB = Image.fromarray(B).convert("L")        
                        RGB = Image.merge("RGB", (imR,imG,imB))
                        RGB.save("stack_%04d_RGB.png" % group)
                        self.image = RGB
                        self.drawingArea.queue_draw()
            
                        if "L" in channels:
                            print "LRGB"
                            H,S,LfromRGB = filters.rgb_to_hsl(RGB)
                            del LfromRGB        
                            R,G,B = filters.hsl_to_rgb(H, S, L)
                            R = Image.fromarray(R).convert("L")
                            G = Image.fromarray(G).convert("L")
                            B = Image.fromarray(B).convert("L")        
                            RGBL = Image.merge("RGB", (R,G,B))
                            RGBL.save("stack_%04d_RGBL.png" % group)
            
                            # Display the stacked image 
                            self.image=RGBL
                            self.drawingArea.queue_draw()
            
        with gtk.gdk.lock:
            self.statusbarLeft.push(self.statusbarLeftID,"Done") 

        self.stack_thread_alive = False
        
    def callback_for_MFBD(self,imageToDisplay,message=""):
        with gtk.gdk.lock:
            # Display the stacked image 
            self.image = imageToDisplay
            self.drawingArea.queue_draw()        
            self.statusbarLeft.push(self.statusbarLeftID,message) 
        return self.alive
        
    def get_page(self):
        hbox = gtk.HBox()
        vbox = gtk.VBox()
        swButtons = gtk.ScrolledWindow()
        swButtons.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        swButtons.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        swButtons.add_with_viewport(vbox)
        hbox.pack_start(swButtons,False,False,0)

        #Create a button to start the image registration
        self.buttonStack = gtk.Button("Stack")
        self.buttonStack.set_flags(gtk.CAN_DEFAULT)
        self.buttonStack.connect("clicked", self.buttonStack_callback)
        vbox.pack_end(self.buttonStack,False,False,0)

        #Create a button to stop the image registration
        self.buttonStop = gtk.Button("Stop")
        self.buttonStop.connect("clicked", self.buttonStop_callback)
        vbox.pack_end(self.buttonStop,False,False,0)

        #Add combobox to select the method
        frame = gtk.Frame("Method:")
        self.comboboxMethod = gtk.combo_box_new_text()
        self.comboboxMethod.append_text("Mean")
        self.comboboxMethod.append_text("Median")
        self.comboboxMethod.append_text("Rank")
        self.comboboxMethod.append_text("Kappa-Sigma")
        self.comboboxMethod.append_text("Kappa-Sigma-Median")
        self.comboboxMethod.append_text("SVD")
        self.comboboxMethod.append_text("Kalman")
        self.comboboxMethod.append_text("Spectral-Rank")
        self.comboboxMethod.append_text("MFBD (Shaplets)")        
        self.comboboxMethod.append_text("Convection")        
        self.comboboxMethod.set_active(0)
        self.comboboxMethod.connect("changed", self.combobox_method_changed)
        frame.add(self.comboboxMethod)
        vbox.pack_start(frame,False,False,0)

        #Add radio buttons to select how colour is handled
        frame = gtk.Frame("Data Model:")
        self.comboboxData = gtk.combo_box_new_text()
        self.comboboxData.append_text("Pixel")
        self.comboboxData.append_text("Fourier")
        self.comboboxData.append_text("Wavelet")
        self.comboboxData.set_active(0)
        self.comboboxData.connect("changed", self.combobox_data_changed)
        frame.add(self.comboboxData)
        vbox.pack_start(frame,False,False,0)        

        #Add radio buttons to select how colour is handled
        frame = gtk.Frame("Colour Model:")
        self.comboboxColour = gtk.combo_box_new_text()
        self.comboboxColour.append_text("Greyscale")
        self.comboboxColour.append_text("RGB")
        self.comboboxColour.append_text("LRGB")
        self.comboboxColour.set_active(0)
        self.comboboxColour.connect("changed", self.combobox_colour_changed)
        frame.add(self.comboboxColour)
        vbox.pack_start(frame,False,False,0)
       
        #Add a check-button to toggle the exposure mask
        self.maskButton = gtk.CheckButton("Mask Empty Values")
        self.maskButton.connect("toggled", self.mask_toggled)
        vbox.pack_start(self.maskButton,False,False,0)

        #Add a check-button to toggle histogram correction preview
        self.histogramCorrectionButton = gtk.CheckButton("Histogram Correction")
        self.histogramCorrectionButton.connect("toggled", self.histogramCorrection_toggled)
        vbox.pack_start(self.histogramCorrectionButton,False,False,0)

        #Add a spin-button to set the black-point
        self.hboxBlackAndWhite = gtk.HBox()
        vbox.pack_start(self.hboxBlackAndWhite, False, False,0)
        self.frameBlack = gtk.Frame("Black (0-1):")
        self.adjustmentBlack = gtk.Adjustment(value=self.blackPoint, lower=0.0, upper=1., step_incr=0.01, page_incr=0.2)
        self.spinbuttonBlack = gtk.SpinButton(self.adjustmentBlack)
        self.spinbuttonBlack.set_digits(2)
        self.spinbuttonBlack.connect("value-changed",self.black_changed)
        self.frameBlack.add(self.spinbuttonBlack)
        self.hboxBlackAndWhite.pack_start(self.frameBlack, False, False,0)

        #Add a spin-button to set the white-point
        self.frameWhite = gtk.Frame("White (0-1):")
        self.adjustmentWhite = gtk.Adjustment(value=self.whitePoint, lower=0.0, upper=1., step_incr=0.01, page_incr=0.2)
        self.spinbuttonWhite = gtk.SpinButton(self.adjustmentWhite)
        self.spinbuttonWhite.set_digits(2)
        self.spinbuttonWhite.connect("value-changed",self.white_changed)
        self.frameWhite.add(self.spinbuttonWhite)
        self.hboxBlackAndWhite.pack_start(self.frameWhite, False, False,0)

        #Add a spin-button to set the gamma correction
        self.frameGamma = gtk.Frame("Gamma:")
        self.adjustmentGamma = gtk.Adjustment(value=self.gammaCorrection, lower=0.01, upper=10., step_incr=0.1, page_incr=0.5)
        self.spinbuttonGamma = gtk.SpinButton(self.adjustmentGamma)
        self.spinbuttonGamma.set_digits(2)
        self.spinbuttonGamma.connect("value-changed",self.gamma_changed)
        self.frameGamma.add(self.spinbuttonGamma)
        vbox.pack_start(self.frameGamma, False, False,0)

        #Add a spin-button to choose to stack images in groups (useful for making animations)
        frame = gtk.Frame("Stack into groups of:")
        self.adjustmentSubGroups = gtk.Adjustment(value=self.stackInGroups, lower=0, upper=len(self.frameList), step_incr=1, page_incr=5)
        self.spinbuttonSubGroups = gtk.SpinButton(self.adjustmentSubGroups)
        self.spinbuttonSubGroups.connect("value-changed",self.stackInGroups_changed)
        frame.add(self.spinbuttonSubGroups)
        vbox.pack_start(frame, False, False,0)

        #Add a spin-button to choose the value of kappa for kappa-sigma methods
        self.frameKappa = gtk.Frame("Kappa:")
        self.adjustmentKappa = gtk.Adjustment(value=self.kappa, lower=0.0, upper=10., step_incr=0.1, page_incr=1)
        self.spinbuttonKappa = gtk.SpinButton(self.adjustmentKappa)
        self.spinbuttonKappa.set_digits(1)
        self.spinbuttonKappa.connect("value-changed",self.kappa_changed)
        self.frameKappa.add(self.spinbuttonKappa)
        vbox.pack_start(self.frameKappa, False, False,0)

        #Add a spin-button to choose the value of percentile for rank stacking
        self.framePercentile = gtk.Frame("Percentile:")
        self.adjustmentPercentile = gtk.Adjustment(value=self.percentile, lower=0.0, upper=1.0, step_incr=0.1, page_incr=0.5)
        self.spinbuttonPercentile = gtk.SpinButton(self.adjustmentPercentile)
        self.spinbuttonPercentile.set_digits(1)
        self.spinbuttonPercentile.connect("value-changed",self.percentile_changed)
        self.framePercentile.add(self.spinbuttonPercentile)
        vbox.pack_start(self.framePercentile, False, False,0)

        #Add a spin-button to choose the number of singular values to use when using SVD stacking
        self.frameSVD = gtk.Frame("Singular Values:")
        self.adjustmentSVD = gtk.Adjustment(value=self.noOfSingularValues, lower=1, upper=100, step_incr=1, page_incr=10)
        self.spinbuttonSVD = gtk.SpinButton(self.adjustmentSVD)
        self.spinbuttonSVD.set_digits(0)
        self.spinbuttonSVD.connect("value-changed",self.svd_changed)
        self.frameSVD.add(self.spinbuttonSVD)
        vbox.pack_start(self.frameSVD, False, False,0)
        
        #Add combobox to select the MFBD initial guess method
        self.frameMFBDinitialGuessMethod = gtk.Frame("Initial guess:")
        self.comboboxMFBDinitialGuessMethod = gtk.combo_box_new_text()
        self.comboboxMFBDinitialGuessMethod.append_text("First frame")
        self.comboboxMFBDinitialGuessMethod.append_text("Mean")
        self.comboboxMFBDinitialGuessMethod.append_text("Median")
        self.comboboxMFBDinitialGuessMethod.set_active(self.MFBD_initialGuessMethod)
        self.comboboxMFBDinitialGuessMethod.connect("changed", self.combobox_MFBDinitialGuessMethod_changed)
        self.frameMFBDinitialGuessMethod.add(self.comboboxMFBDinitialGuessMethod)
        vbox.pack_start(self.frameMFBDinitialGuessMethod,False,False,0)

        #Add a spin-button to choose to number of frames when using MFBD
        self.frameMFBDnoOfFrames = gtk.Frame("Frames to deconvolve:")
        self.adjustmentNoOfFrames = gtk.Adjustment(value=self.MFBD_noOfFrames, lower=1, upper=len(self.frameList), step_incr=1, page_incr=5)
        self.spinbuttonNoOfFrames = gtk.SpinButton(self.adjustmentNoOfFrames)
        self.adjustmentNoOfFrames.set_value(self.MFBD_noOfFrames)
        self.spinbuttonNoOfFrames.connect("value-changed",self.noOfFrames_changed)
        self.frameMFBDnoOfFrames.add(self.spinbuttonNoOfFrames)
        vbox.pack_start(self.frameMFBDnoOfFrames, False, False,0)
        
        #Add a spin-button to choose MFBD PSF size
        self.frameMFBDPSFSize = gtk.Frame("PSF size:")
        self.adjustmentMFBDPSFSize = gtk.Adjustment(value=self.MFBD_psfSize, lower=4, upper=min(self.width,self.height), step_incr=1, page_incr=8)
        self.spinbuttonMFBDPSFSize = gtk.SpinButton(self.adjustmentMFBDPSFSize)
        self.spinbuttonMFBDPSFSize.connect("value-changed",self.psfSize_changed)
        self.frameMFBDPSFSize.add(self.spinbuttonMFBDPSFSize)
        vbox.pack_start(self.frameMFBDPSFSize, False, False,0)

        #Add a spin-button to choose to initial blur diameter
        self.frameMFBDshapeletSize = gtk.Frame("Shapelet size:")
        self.adjustmentMFBDshapeletSize = gtk.Adjustment(value=self.MFBD_shapeletSize, lower=1., upper=self.MFBD_psfSize, step_incr=1, page_incr=10)
        self.spinbuttonMFBDshapeletSize = gtk.SpinButton(self.adjustmentMFBDshapeletSize)
        self.spinbuttonMFBDshapeletSize.connect("value-changed",self.MFBDshapeletSize_changed)
        self.frameMFBDshapeletSize.add(self.spinbuttonMFBDshapeletSize)
        vbox.pack_start(self.frameMFBDshapeletSize, False, False,0)

        self.frameMFBDShapeletOrder = gtk.Frame("Shapelet levels:")
        self.adjustmentMFBDShapeletOrder = gtk.Adjustment(value=self.MFBD_shapeletOrder, lower=1, upper=100, step_incr=1, page_incr=5)
        self.spinbuttonMFBDShapeletOrder = gtk.SpinButton(self.adjustmentMFBDShapeletOrder)
        self.spinbuttonMFBDShapeletOrder.connect("value-changed",self.MFBDShapeletOrder_changed)
        self.frameMFBDShapeletOrder.add(self.spinbuttonMFBDShapeletOrder)
        vbox.pack_start(self.frameMFBDShapeletOrder, False, False,0)

        #Add a spin-button to choose to number of E-steps when using MFBD
        self.frameMFBDEiterations = gtk.Frame("Fitting iters:")
        self.adjustmentEiterations = gtk.Adjustment(value=self.MFBD_Eiterations, lower=1, upper=100, step_incr=1, page_incr=10)
        self.spinbuttonEiterations = gtk.SpinButton(self.adjustmentEiterations)
        self.spinbuttonEiterations.connect("value-changed",self.Eiterations_changed)
        self.frameMFBDEiterations.add(self.spinbuttonEiterations)
        vbox.pack_start(self.frameMFBDEiterations, False, False,0)

        #Add a spin-button to choose to number of E-steps when using MFBD
        self.frameMFBDMiterations = gtk.Frame("Deconv. iters:")
        self.adjustmentMiterations = gtk.Adjustment(value=self.MFBD_Miterations, lower=1, upper=100, step_incr=1, page_incr=10)
        self.spinbuttonMiterations = gtk.SpinButton(self.adjustmentMiterations)
        self.spinbuttonMiterations.connect("value-changed",self.Miterations_changed)
        self.frameMFBDMiterations.add(self.spinbuttonMiterations)
        vbox.pack_start(self.frameMFBDMiterations, False, False,0)

        #Add a spin-button to choose the LR threshold
        self.frameMFBDLRThreshold = gtk.Frame("LR Threshold:")
        self.adjustmentMFBDLRThreshold = gtk.Adjustment(value=self.MFBD_LRThreshold, lower=0., upper=255., step_incr=1, page_incr=10)
        self.spinbuttonMFBDLRThreshold = gtk.SpinButton(self.adjustmentMFBDLRThreshold)
        self.spinbuttonMFBDLRThreshold.set_digits(2)
        self.spinbuttonMFBDLRThreshold.connect("value-changed",self.MFBDLRThreshold_changed)
        self.frameMFBDLRThreshold.add(self.spinbuttonMFBDLRThreshold)
        vbox.pack_start(self.frameMFBDLRThreshold, False, False,0)

        #Add a spin-button to choose the LR threshold
        self.frameMFBDTVScaling = gtk.Frame("Spectral denoise:")
        self.adjustmentMFBDTVScaling = gtk.Adjustment(value=self.MFBD_spectralNoiseFilter, lower=0., upper=1.0, step_incr=0.01, page_incr=0.5)
        self.spinbuttonMFBDTVScaling = gtk.SpinButton(self.adjustmentMFBDTVScaling)
        self.spinbuttonMFBDTVScaling.set_digits(3)
        self.spinbuttonMFBDTVScaling.connect("value-changed",self.MFBDTVScaling_changed)
        self.frameMFBDTVScaling.add(self.spinbuttonMFBDTVScaling)
        vbox.pack_start(self.frameMFBDTVScaling, False, False,0)

        #Add a check-button to toggle wavelet filter for MFBD
        self.MFBDwaveletThresholdButton = gtk.CheckButton("Wavelet Denoise")
        self.MFBDwaveletThresholdButton.connect("toggled", self.MFBDwaveletThreshold_toggled)
        vbox.pack_start(self.MFBDwaveletThresholdButton,False,False,0)
        
        #Add a spin-button to choose to convection distance using MFBC
        self.frameDistance = gtk.Frame("Convection distance:")
        self.adjustmentDistance = gtk.Adjustment(value=self.MFBC_distance, lower=1, upper=100, step_incr=1, page_incr=10)
        self.spinbuttonDistance = gtk.SpinButton(self.adjustmentDistance)
        self.spinbuttonDistance.connect("value-changed",self.distance_changed)
        self.frameDistance.add(self.spinbuttonDistance)
        vbox.pack_start(self.frameDistance, False, False,0)

        #Add scales to set the parameters for the spectral rank method.
        self.frameSpectralMidFreq = gtk.Frame("Middle Freq (DC to 1/px)")
        self.adjSpectralMidFreq   = gtk.Adjustment(value=self.spectralMidFrequency, lower=0.01, upper=0.99, step_incr=0.01,page_incr=0.1)
        self.scaleSpectralMidFreq = gtk.HScale(self.adjSpectralMidFreq)
        self.scaleSpectralMidFreq.connect("value-changed",self.spectralMidFreq_changed)
        self.frameSpectralMidFreq.add(self.scaleSpectralMidFreq)
        vbox.pack_start(self.frameSpectralMidFreq,False,False,0)
        
        self.frameSpectralMidRank = gtk.Frame("Middle Freq Rank")
        self.adjSpectralMidRank   = gtk.Adjustment(value=self.spectralMidRank, lower=0., upper=1., step_incr=0.1,page_incr=0.2)
        self.scaleSpectralMidRank = gtk.HScale(self.adjSpectralMidRank)
        self.scaleSpectralMidRank.connect("value-changed",self.spectralMidRank_changed)
        self.frameSpectralMidRank.add(self.scaleSpectralMidRank)
        vbox.pack_start(self.frameSpectralMidRank,False,False,0)

        self.frameSpectralHighRank = gtk.Frame("High Freq Rank")
        self.adjSpectralHighRank   = gtk.Adjustment(value=self.spectralHighRank, lower=0., upper=1., step_incr=0.1,page_incr=0.2)
        self.scaleSpectralHighRank = gtk.HScale(self.adjSpectralHighRank)
        self.scaleSpectralHighRank.connect("value-changed",self.spectralHighRank_changed)
        self.frameSpectralHighRank.add(self.scaleSpectralHighRank)
        vbox.pack_start(self.frameSpectralHighRank,False,False,0)

        #Construct the capture view area
        scrolledWindow = gtk.ScrolledWindow()
        scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)          
        self.drawingArea = gtk.DrawingArea()
        self.drawingArea.set_size_request(self.width, self.height)
        scrolledWindow.add_with_viewport(self.drawingArea)         
        hbox.pack_end(scrolledWindow, expand=True, fill=True, padding=0)
        self.drawingArea.show()
        
        # Signals used to handle backing pixmap
        self.drawingArea.connect("configure_event", self.configure_event)
        self.drawingArea.connect("expose_event", self.expose_event)

        # Event signals
        self.drawingArea.connect("button_press_event", self.button_press_event)
        self.drawingArea.connect("button_release_event", self.button_release_event)
        self.drawingArea.connect("motion_notify_event", self.motion_notify_event)
        
        self.drawingArea.set_events(gtk.gdk.EXPOSURE_MASK
                                | gtk.gdk.LEAVE_NOTIFY_MASK
                                | gtk.gdk.BUTTON_PRESS_MASK
                                | gtk.gdk.BUTTON_RELEASE_MASK
                                | gtk.gdk.POINTER_MOTION_MASK
                                | gtk.gdk.POINTER_MOTION_HINT_MASK)
        
        hbox.show_all()

        #Hide the options for inactive methods
        self.show_necessary_controls()

        return hbox

    def image2pixbuf(self,im):    
        if im:
            file1 = StringIO.StringIO()  
            im.save(file1, "ppm")  
            contents = file1.getvalue()  
            file1.close()  
            loader = gtk.gdk.PixbufLoader("pnm")  
            loader.write(contents, len(contents))  
            pixbuf = loader.get_pixbuf()  
            loader.close()
            return pixbuf
